-- npc.lua
-- Available functions are:
-- RG_npc.create(x, y, z, size, hp, name)
-- RG_npc.move(unit, x, y)
-- RG_npc.damage(unit, amount)
-- RG_npc.attack(attacker, victim, amount)
-- RG_npc.info(unit)
-- RG_npc.time()

npcs = {}

-- Helper function to create and keep track of all the NPCs
createNPC = function(x, y, z, size, hp, name, func)
    local new_npc = RG_npc.create(x, y, z, name, size, hp)
    local npc_t = {i=new_npc, f=func}
    table.insert(npcs, npc_t)
    return new_npc
end

-- Returns true iff the first NPC given is closer to (0,0,0) than the second
closerToOrigin = function(npc1, npc2)
    local a = RG_npc.info(npc1.i)
    local b = RG_npc.info(npc2.i)
    return math.sqrt(a.x*a.x + a.y*a.y + a.z*a.z) < math.sqrt(b.x*b.x + b.y*b.y + b.z*b.z)
end

-- ROWGAGE-required function to enable each NPC to act each turn cycle
npcTurn = function()
    --table.sort(npcs, closerToOrigin)
    for k,v in pairs(npcs) do
        v.f(v.i)
    end
end

--
-- AI-driven Agent code below this line:
--

-- Returns the distance between 2 tables with members x, y, and z
distance = function(a, b)
    return math.sqrt( (a.x - b.x)^2 + (a.y - b.y)^2 + (a.z - b.z)^2 )
end

-- Returns the amount of time (in seconds) elapsed since the given timestamp
timeElapsed = function(time)
    return RG_npc.time() - time
end

nearbyEntities = function(this)
    local close = {}
    local me = RG_npc.info(this)
    for k,v in pairs(npcs) do
        if v.i ~= this then
            local somenpc = RG_npc.info(v.i)
            if somenpc.hp > 0 and distance(somenpc, me) < 200 then
                table.insert(close, v.i)
            end
        end
    end
    if distance(RG_npc.info(-1), me) < 200 then
        table.insert(close, -1)
    end
    return close
end

-- Creates a new cat monster at the given x/y coordinates
catMonster = function(x, y)
    local state = {}
    createNPC(x, y, 0, 10, 100, "Menacing cat monster", function(this)
        if state.timer == nil then
            state.timer = RG_npc.time()
            state.swingtimer = state.timer
            state.hungertimer = state.timer
            state.mode = 0 -- 0 = wander, 1 = avoid, 2 = flee, 3 = attack
            state.hunger = 0 -- Start off well fed
        end
        if timeElapsed(state.timer) > 1/10 then -- Limit our agent to max 30 actions/sec
            state.timer = RG_npc.time()
            state.near = nearbyEntities(this)
            local me = RG_npc.info(this)
            if me.hp > 0 then -- Dead agents can't do much
                -- Step 1: Figure out which mode we should be in
                --if #state.near == 0 then
                if table.getn(state.near) == 0 then
                    state.mode = 0
                elseif state.mode == 3 then
                    -- If I'm attacking I'm not stopping until it's dead
                --elseif #state.near == 1 then
                elseif table.getn(state.near) == 1 then
                    local entity = RG_npc.info(state.near[1])
                    if state.hunger > 100 then
                        -- Starving, attack no matter what
                        state.mode = 3
                        state.target = state.near[1]
                    elseif state.hunger*2 + me.hp > entity.hp*2 then
                        -- Looks like this thing needs to be eaten
                        state.mode = 3
                        state.target = state.near[1]
                    elseif distance(me, entity) < 100 then
                        -- Too close and I'm not eating it, time to cheese it
                        state.mode = 2
                        state.fleeVector = {}
                        state.fleeVector.x = me.x - entity.x 
                        state.fleeVector.y = me.y - entity.y
                        state.fleeVector.z = me.z - entity.z
                    else
                        state.mode = 1
                        state.fleeVector = {}
                        state.fleeVector.x = me.x - entity.x
                        state.fleeVector.y = me.y - entity.y
                        state.fleeVector.z = me.z - entity.z
                    end
                else
                    -- Many nearby entities, need to leave
                    state.fleeVector = {x=0, y=0, z=0}
                    local needToFlee = false
                    for i,v in pairs(state.near) do
                        local entity = RG_npc.info(v)
                        state.fleeVector.x = state.fleeVector.x + (me.x - entity.x)
                        state.fleeVector.y = state.fleeVector.y + (me.y - entity.y)
                        state.fleeVector.z = state.fleeVector.z + (me.z - entity.z)
                        if distance(me, entity) < 100 then
                            -- Something's too close, cheese it!
                            needToFlee = true
                        end
                    end
                    state.fleeVector.x = state.fleeVector.x / table.getn(state.near)
                    state.fleeVector.y = state.fleeVector.y / table.getn(state.near)
                    state.fleeVector.z = state.fleeVector.z / table.getn(state.near)
                end
                -- Step 2: Given our mode, figure out what we should do
                if state.mode == 0 then
                    -- Wander mode
                    state.lastwander = state.lastwander or
                        {x=math.random(-1,1), y=math.random(-1,1), z=0}
                    if math.random() > 0.9 then
                        if state.lastwander.x == 0 then
                            state.lastwander.x = math.random(-1,1)
                        else
                            state.lastwander.x = 0
                        end
                    end
                    if math.random() > 0.9 then
                        if state.lastwander.y == 0 then
                            state.lastwander.y = math.random(-1,1)
                        else
                            state.lastwander.y = 0
                        end
                    end
                    RG_npc.move(this, state.lastwander.x, state.lastwander.y)
                elseif state.mode == 1 then
                    -- Avoid mode
                    state.fleeVector.x = state.fleeVector.x or 0
                    state.fleeVector.y = state.fleeVector.y or 0
                    state.fleeVector.z = state.fleeVector.z or 0
                    local scale = math.abs(state.fleeVector.x)
                    local scale2 = math.abs(state.fleeVector.y)
                    scale = scale > scale2 and scale or scale2
                    scale2 = math.abs(state.fleeVector.z)
                    scale = scale > scale2 and scale or scale2
                    if scale ~= 0 then
                        state.fleeVector.x = (state.fleeVector.x / scale) * 1
                        state.fleeVector.y = (state.fleeVector.y / scale) * 1
                        state.fleeVector.z = (state.fleeVector.z / scale) * 1
                    end
                    RG_npc.move(this, state.fleeVector.x, state.fleeVector.y)
                elseif state.mode == 2 then
                    -- Flee mode
                    state.fleeVector.x = state.fleeVector.x or 0
                    state.fleeVector.y = state.fleeVector.y or 0
                    state.fleeVector.z = state.fleeVector.z or 0
                    local scale = math.abs(state.fleeVector.x)
                    local scale2 = math.abs(state.fleeVector.y)
                    scale = scale > scale2 and scale or scale2
                    scale2 = math.abs(state.fleeVector.z)
                    scale = scale > scale2 and scale or scale2
                    if scale ~= 0 then
                        state.fleeVector.x = (state.fleeVector.x / scale) * 3
                        state.fleeVector.y = (state.fleeVector.y / scale) * 3
                        state.fleeVector.z = (state.fleeVector.z / scale) * 3
                    end
                    RG_npc.move(this, state.fleeVector.x, state.fleeVector.y)
                elseif state.mode == 3 then
                    -- Attack mode
                    local target = RG_npc.info(state.target)
                    if target.hp > 0 then
                        local delta = distance(target, me)
                        local x = target.x - me.x
                        local y = target.y - me.y
                        x = x > 3 and 3 or x
                        x = x < -3 and -3 or x
                        y = y > 3 and 3 or y
                        y = y < -3 and -3 or y
                        RG_npc.move(this, x, y)
                        if delta < 32 and timeElapsed(state.swingtimer) > 1 then
                            RG_npc.attack(this, state.target, 15)
                            -- Sure, you generally need to kill something
                            -- before you can start eating it, but that's just
                            -- needless complication
                            state.hunger = state.hunger - 10
                            state.swingtimer = RG_npc.time()
                        end
                    else
                        state.mode = 0
                    end
                end
                -- Step 3: Check if we need to update the hungry meter
                if timeElapsed(state.hungertimer) > 1 then
                    -- Get a little hungrier
                    state.hunger = state.hunger + 2
                    state.hungertimer = RG_npc.time()
                end
            end
        end
    end)
end

--
-- Create all our instances of the NPCs below this line:
--

catMonster(500, 500)
catMonster(350, 500)
